package nginx

import (
	"bufio"
	"io"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"gitee.com/bjf-fhe/apicat/entry"
	"gitee.com/bjf-fhe/apicat/notify"
	"github.com/sirupsen/logrus"
)

//Parse 读取原有配置
func Parse(fileName string) (map[string]*entry.LogEntry, error) {
	readFile, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer readFile.Close()
	var result = parseReader(readFile)
	return result, nil
}

//parse item from existed config, last cause entry and client ip
func parseReader(reader io.Reader) map[string]*entry.LogEntry {

	fileScanner := bufio.NewScanner(reader)
	fileScanner.Split(bufio.ScanLines)

	var results = make(map[string]*entry.LogEntry)

	var currentEntry *entry.LogEntry
	for fileScanner.Scan() {
		var line = fileScanner.Text()
		if strings.HasPrefix(line, "#") {
			currentEntry = entry.ParseString(line)
		} else if strings.HasPrefix(line, "deny ") {
			if currentEntry == nil {
				currentEntry = new(entry.LogEntry)
			}
			currentEntry.Client = strings.TrimSuffix(strings.TrimPrefix(line, "deny "), ";")
			results[currentEntry.Client] = currentEntry
			currentEntry = nil
		}
	}

	return results
}

func (h *Handler) writeToDest(nes map[string]*entry.LogEntry) (err error) {
	h.mutex.Lock()
	h.writer, err = os.OpenFile(h.ncfg.Dest, os.O_TRUNC|os.O_WRONLY, 0600)
	if err != nil {
		logrus.Errorln("reopen destination error", err)
	}
	defer func() {
		h.writer.Close()
		h.mutex.Unlock()
	}()
	return h.writeToWriter(nes, h.writer)
}

func (h *Handler) writeToWriter(nes map[string]*entry.LogEntry, wr io.Writer) (err error) {
	for _, v := range nes {
		if v.Count >= int64(h.cfg.MinCount) && v.ErrorType >= entry.EntryError(h.cfg.MinLevel) {
			logrus.Debug("write item:", v.String())
			v.WriteTo(wr, "deny "+v.Client+";\n")
			if err != nil {
				return err
			}
		}
	}
	h.writer.Close()

	return err
}

type Config struct {
	Dest     string
	Interval int64
	WorkDir  string
	Cmd      string
}

type Handler struct {
	duration      time.Duration
	lastWriteTime time.Time
	writer        *os.File
	waiting       bool
	cache         chan map[string]*entry.LogEntry
	cfg           *notify.Config
	ncfg          *Config
	mutex         *sync.Mutex
}

func (h *Handler) Init(cfg *notify.Config, ncfg *Config) (err error) {
	h.duration = time.Second * time.Duration(ncfg.Interval)
	h.cache = make(chan map[string]*entry.LogEntry, 1)
	h.cfg = cfg
	h.ncfg = ncfg
	h.mutex = &sync.Mutex{}
	return err
}

func (h *Handler) Notify(ens map[string]*entry.LogEntry) error {
	var now = time.Now()
	if now.Sub(h.lastWriteTime) > h.duration {
		err := h.writeToDest(ens)
		if err == nil {
			h.reload()
			h.lastWriteTime = now
		}
		return err
	} else {
		if h.waiting {
			<-h.cache
		} else {
			go func() {
				<-time.After(h.lastWriteTime.Add(h.duration).Sub(now))
				h.writeCache()
			}()
		}
		h.cache <- ens
		h.waiting = true
	}
	return nil
}

func (h *Handler) reload() {
	if h.ncfg.Cmd == "" {
		logrus.Infoln("Nginx重启命令为空，不执行重启指令")
		return
	}
	var parts = strings.Split(h.ncfg.Cmd, " ")
	var cmd = exec.Command(parts[0], parts[1:]...)
	cmd.Dir = h.ncfg.WorkDir
	err := cmd.Run()
	if err != nil {
		logrus.Errorln("restart nginx error", err)
	}
}

func (h *Handler) writeCache() {
	cached := <-h.cache
	err := h.writeToDest(cached)
	if err == nil {
		h.reload()
	}
	h.lastWriteTime = time.Now()
	h.waiting = false
}
